//
//  IDDCacheManager.m
//  ComJia
//
//  Created by Aslan on 17/02/2017.
//  Copyright © 2017 ComJia. All rights reserved.
//

#import "IDDCacheManager.h"
#import "IDDMemoryCacheManager_Private.h"
#import "IDDCacheManager_Private.h"
#import "IDDObjectCacheManager.h"
#import "IDDURLCacheManager.h"

static IDDCacheManagerStrategy URLCacheStrategy;
static IDDCacheManagerStrategy ObjectCacheStrategy;

@implementation IDDCacheEntity

- (instancetype)initWithCache:(id)cache forKey:(NSString *)key atPath:(NSString *)path andType:(IDDCacheEntityType)type
{
    self = [super init];
    if (self) {
        _cache = cache;
        _key = key;
        _path = path;
        _type = type;
        _date = [NSDate date];
    }
    return self;
}

@end

@implementation IDDCacheManager

@synthesize privateCacheDirectory = _privateCacheDirectory;
@synthesize publicCacheDirectory = _publicCacheDirectory;

- (instancetype)initWithStrategy:(IDDCacheManagerStrategy)strategy queueName:(char *)queueName
{
    self = [super init];
    if (self) {
        _memoryCacheManager = [[IDDMemoryCacheManager alloc] init];
        _queue = dispatch_queue_create(queueName, DISPATCH_QUEUE_CONCURRENT);
        _strategy = strategy;
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didEnterBackground) name:UIApplicationDidEnterBackgroundNotification object:nil];
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(willTerminate) name:UIApplicationWillTerminateNotification object:nil];
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(willEnterForeground) name:UIApplicationWillEnterForegroundNotification object:nil];
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(dumpMemoryCache) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
    }
    return self;
}

- (void)dealloc
{
    [[NSNotificationCenter defaultCenter] removeObserver:self name:nil object:nil];
}

- (void)setPublicCacheDirectory:(NSString *)publicCacheDirectory
{
    publicCacheDirectory = publicCacheDirectory.length ? publicCacheDirectory : @"public";
    dispatch_barrier_sync(_queue, ^{
        _memoryCacheManager.publicCacheDirectory = publicCacheDirectory;
        _persistentCacheManager.publicCacheDirectory = publicCacheDirectory;
    });
}

- (void)setPrivateCacheDirectory:(NSString *)privateCacheDirectory
{
    privateCacheDirectory = privateCacheDirectory.length ? privateCacheDirectory : @"user";
    dispatch_barrier_sync(_queue, ^{
        _memoryCacheManager.privateCacheDirectory = privateCacheDirectory;
        _persistentCacheManager.privateCacheDirectory = privateCacheDirectory;
    });
}

+ (void)setURLCacheStrategy:(IDDCacheManagerStrategy)strategy
{
    URLCacheStrategy = strategy;
}

+ (void)setObjectCacheStrategy:(IDDCacheManagerStrategy)strategy
{
    ObjectCacheStrategy = strategy;
}

+ (IDDCacheManager *)sharedObjectCacheManager
{
    static IDDCacheManager *sharedObjectCacheManager;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        if (ObjectCacheStrategy == IDDCacheManagerStrategyUnknown) {
            ObjectCacheStrategy = IDDCacheManagerStrategyWriteThrough;
        }
        sharedObjectCacheManager = [[IDDCacheManager alloc] initWithStrategy:ObjectCacheStrategy queueName:"com.iddkit.dispatchQueue.objectCacheManager"];
        sharedObjectCacheManager.persistentCacheManager = [[IDDObjectCacheManager alloc] init];
    });
    return sharedObjectCacheManager;
}

+ (IDDCacheManager *)sharedURLCacheManager
{
    static IDDCacheManager *sharedURLCacheManager;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        if (URLCacheStrategy == IDDCacheManagerStrategyUnknown) {
            URLCacheStrategy = IDDCacheManagerStrategyWriteBack;
        }
        sharedURLCacheManager = [[IDDCacheManager alloc] initWithStrategy:URLCacheStrategy queueName:"com.iddkit.dispatchQueue.URLCacheManager"];
        sharedURLCacheManager.persistentCacheManager = [[IDDURLCacheManager alloc] init];
    });
    return sharedURLCacheManager;
}

- (unsigned long long)cacheSizeAtPath:(NSString *)path
{
    return 0;
}

- (unsigned long long)cacheSize
{
    __block unsigned long long cacheSize = 0;
    dispatch_sync(_queue, ^{
        cacheSize = [_persistentCacheManager cacheSize];
    });
    return cacheSize;
}

- (unsigned long long)privateCacheSizeAtPath:(NSString *)path
{
    __block unsigned long long cacheSize = 0;
    dispatch_sync(_queue, ^{
        cacheSize = [_persistentCacheManager privateCacheSizeAtPath:path];
    });
    return cacheSize;
}

- (unsigned long long)publicCacheSizeAtPath:(NSString *)path
{
    __block unsigned long long cacheSize = 0;
    dispatch_sync(_queue, ^{
        cacheSize = [_persistentCacheManager publicCacheSizeAtPath:path];
    });
    return cacheSize;
}

- (void)cleanCache
{
    dispatch_barrier_sync(_queue, ^{
        [_memoryCacheManager cleanCache];
        [_persistentCacheManager cleanCache];
    });
}

- (void)cleanPrivateCacheAtPath:(NSString *)path
{
    dispatch_barrier_sync(_queue, ^{
        [_memoryCacheManager cleanPrivateCacheAtPath:path];
        [_persistentCacheManager cleanPrivateCacheAtPath:path];
    });
}

- (void)cleanPublicCacheAtPath:(NSString *)path
{
    dispatch_barrier_sync(_queue, ^{
        [_memoryCacheManager cleanPublicCacheAtPath:path];
        [_persistentCacheManager cleanPublicCacheAtPath:path];
    });
}

- (IDDCacheEntity *)setEntity:(IDDCacheEntity *)entity error:(IDDError *__autoreleasing *)error
{
    __block IDDError *thisError = nil;
    if (_strategy == IDDCacheManagerStrategyWriteBack) {
        __block IDDCacheEntity *staleEntity = nil;
        dispatch_barrier_sync(_queue, ^{
            staleEntity = [_memoryCacheManager setEntity:entity error:&thisError];
        });
        if (thisError) {
            if (error) {
                *error = thisError;
            }
            return nil;
        }
        if (staleEntity) {
            dispatch_barrier_sync(_queue, ^{
                [_persistentCacheManager setEntity:staleEntity error:&thisError];
            });
        }
        return entity;
    } else {
        __block IDDCacheEntity *resultEntity = nil;
        dispatch_barrier_sync(_queue, ^{
            [_memoryCacheManager setEntity:entity error:&thisError];
            if (thisError) {
                if (error) {
                    *error = thisError;
                }
                return;
            }
            resultEntity = [_persistentCacheManager setEntity:entity error:error];
        });
        return resultEntity;
    }
}

- (IDDCacheEntity *)privateCacheForKey:(NSString *)key error:(IDDError *__autoreleasing *)error
{
    return [self privateCacheForKey:key atPath:nil error:error];
}

- (IDDCacheEntity *)privateCacheForKey:(NSString *)key atPath:(NSString *)path error:(IDDError *__autoreleasing *)error
{
    if (error) {
        *error = nil;
    }
    __block IDDCacheEntity *entity = nil;
    __block IDDError *thisError = nil;
    dispatch_sync(_queue, ^{
        entity = [_memoryCacheManager privateCacheForKey:key atPath:path error:&thisError];
        if (thisError) {
            return;
        }
        if (entity) {
            return;
        }
        entity = [_persistentCacheManager privateCacheForKey:key atPath:path error:&thisError];
        if (entity) {
            return;
        }
    });
    if (!entity) {
        if (thisError && error) {
            *error = thisError;
        }
        return nil;
    }
    __block IDDCacheEntity *staleEntity = nil;
    dispatch_barrier_sync(_queue, ^{
        staleEntity = [_memoryCacheManager setEntity:entity error:&thisError];
    });
    if (thisError) {
        if (error) {
            *error = thisError;
        }
        return nil;
    }
    if (staleEntity && _strategy == IDDCacheManagerStrategyWriteBack) {
        dispatch_barrier_sync(_queue, ^{
            [_persistentCacheManager setEntity:staleEntity error:error];
        });
    }
    return entity;
}

- (void)privateCacheForKey:(NSString *)key finished:(IDDCacheManagerCallback)finished
{
    [self privateCacheForKey:key atPath:nil finished:finished];
}

- (void)privateCacheForKey:(NSString *)key atPath:(NSString *)path finished:(IDDCacheManagerCallback)finished
{
    dispatch_async(_queue, ^{
        IDDError *error = nil;
        IDDCacheEntity *entity = nil;
        entity = [_memoryCacheManager privateCacheForKey:key atPath:path error:&error];
        dispatch_queue_t queue = self.callbackInMainThread ? dispatch_get_main_queue() : dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        if (error) {
            dispatch_async(queue, ^{
                finished(nil, error);
            });
        } else if (entity) {
            dispatch_async(queue, ^{
                finished(entity, nil);
            });
        } else { // cache not found in memory, we need to dig a little deeper
            dispatch_async(_queue, ^{
                IDDError *error = nil;
                IDDCacheEntity *entity = nil;
                entity = [_persistentCacheManager privateCacheForKey:key atPath:path error:&error];
                if (error) {
                    dispatch_async(queue, ^{
                        finished(nil, error);
                    });
                } else if (entity) { // we find it, save it in memory
                    dispatch_barrier_async(_queue, ^{
                        IDDError *error = nil;
                        IDDCacheEntity *staleEntity = [_memoryCacheManager setEntity:entity error:&error];
                        if (staleEntity && _strategy == IDDCacheManagerStrategyWriteBack) {
                            [_persistentCacheManager setEntity:staleEntity error:&error];
                        }
                    });
                    dispatch_async(queue, ^{
                        finished(entity, nil);
                    });
                } else {
                    dispatch_async(queue, ^{
                        finished(nil, nil);
                    });
                }
            });
        }
    });
}

- (IDDCacheEntity *)setPrivateCache:(id)cache forKey:(NSString *)key error:(IDDError *__autoreleasing *)error
{
    return [self setPrivateCache:cache forKey:key atPath:nil error:error];
}

- (IDDCacheEntity *)setPrivateCache:(id)cache forKey:(NSString *)key atPath:(NSString *)path error:(IDDError *__autoreleasing *)error
{
    IDDCacheEntity *entity = [[IDDCacheEntity alloc] initWithCache:cache forKey:key atPath:path andType:IDDCacheEntityTypePrivate];
    return [self setEntity:entity error:error];
}

- (IDDCacheEntity *)publicCacheForKey:(NSString *)key error:(IDDError *__autoreleasing *)error
{
    return [self publicCacheForKey:key atPath:nil error:error];
}

- (IDDCacheEntity *)publicCacheForKey:(NSString *)key atPath:(NSString *)path error:(IDDError *__autoreleasing *)error
{
    if (error) {
        *error = nil;
    }
    __block IDDCacheEntity *entity = nil;
    __block IDDError *thisError = nil;
    dispatch_sync(_queue, ^{
        entity = [_memoryCacheManager publicCacheForKey:key atPath:path error:&thisError];
        if (thisError) {
            return;
        }
        if (entity) {
            return;
        }
        entity = [_persistentCacheManager publicCacheForKey:key atPath:path error:&thisError];
        if (entity) {
            return;
        }
    });
    if (!entity) {
        if (thisError && error) {
            *error = thisError;
        }
        return nil;
    }
    __block IDDCacheEntity *staleEntity = nil;
    dispatch_barrier_sync(_queue, ^{
        staleEntity = [_memoryCacheManager setEntity:entity error:&thisError];
    });
    if (thisError) {
        if (error) {
            *error = thisError;
        }
        return nil;
    }
    if (staleEntity && _strategy == IDDCacheManagerStrategyWriteBack) {
        dispatch_barrier_sync(_queue, ^{
            [_persistentCacheManager setEntity:staleEntity error:error];
        });
    }
    return entity;
}

- (void)publicCacheForKey:(NSString *)key finished:(IDDCacheManagerCallback)finished
{
    [self publicCacheForKey:key atPath:nil finished:finished];
}

- (void)publicCacheForKey:(NSString *)key atPath:(NSString *)path finished:(IDDCacheManagerCallback)finished
{
    dispatch_async(_queue, ^{
        IDDError *error = nil;
        IDDCacheEntity *entity = nil;
        entity = [_memoryCacheManager publicCacheForKey:key atPath:path error:&error];
        dispatch_queue_t queue = self.callbackInMainThread ? dispatch_get_main_queue() : dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        if (error) {
            dispatch_async(queue, ^{
                finished(nil, error);
            });
        } else if (entity) {
            dispatch_async(queue, ^{
                finished(entity, nil);
            });
        } else {
            dispatch_async(_queue, ^{
                IDDError *error = nil;
                IDDCacheEntity *entity = nil;
                entity = [_persistentCacheManager publicCacheForKey:key atPath:path error:&error];
                if (error) {
                    dispatch_async(queue, ^{
                        finished(nil, error);
                    });
                } else if (entity) {
                    dispatch_barrier_async(_queue, ^{
                        IDDError *error = nil;
                        IDDCacheEntity *staleEntity = [_memoryCacheManager setEntity:entity error:&error];
                        if (staleEntity && _strategy == IDDCacheManagerStrategyWriteBack) {
                            [_persistentCacheManager setEntity:staleEntity error:&error];
                        }
                    });
                    dispatch_async(queue, ^{
                        finished(entity, nil);
                    });
                } else {
                    dispatch_async(queue, ^{
                        finished(nil, nil);
                    });
                }
            });
        }
    });
}

- (IDDCacheEntity *)setPublicCache:(id)cache forKey:(NSString *)key error:(IDDError *__autoreleasing *)error
{
    return [self setPublicCache:cache forKey:key atPath:nil error:error];
}

- (IDDCacheEntity *)setPublicCache:(id)cache forKey:(NSString *)key atPath:(NSString *)path error:(IDDError *__autoreleasing *)error
{
    IDDCacheEntity *entity = [[IDDCacheEntity alloc] initWithCache:cache forKey:key atPath:path andType:IDDCacheEntityTypePublic];
    return [self setEntity:entity error:error];
}

- (void)saveMemoryCache
{
    if (_strategy == IDDCacheManagerStrategyWriteThrough) {
        return;
    }
    dispatch_suspend(_queue);
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [_memoryCacheManager.memoryCache
         enumerateKeysAndObjectsWithOptions:NSEnumerationConcurrent
         usingBlock:^(id key, IDDCacheEntity *entity, BOOL *stop) {
             IDDError *error = nil;
             if (!entity.dirty) {
                 return;
             }
             if (entity.type == IDDCacheEntityTypePrivate) {
                 [_persistentCacheManager setPrivateCache:entity.cache forKey:entity.key atPath:entity.path error:&error];
             } else if (entity.type == IDDCacheEntityTypePublic) {
                 [_persistentCacheManager setPublicCache:entity.cache forKey:entity.key atPath:entity.path error:&error];
             }
             if (error) {
                 *stop = YES;
             }
             if (!_inBackground) {
                 *stop = YES;
             }
         }];
        dispatch_resume(_queue);
    });
}

- (void)dumpMemoryCache
{
    [self saveMemoryCache];
    dispatch_barrier_sync(_queue, ^{
        [self.memoryCacheManager cleanCache];
    });
}

- (void)didEnterBackground
{
    if (_inBackground) {
        return;
    }
    _inBackground = YES;
    [self saveMemoryCache];
}

- (void)willTerminate
{
    [self saveMemoryCache];
}

- (void)willEnterForeground
{
    if (!_inBackground) {
        return;
    }
    _inBackground = NO;
}

@end
